package com.openlogic.auth.service;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

import org.noear.nami.NamiAttachment;
import org.noear.nami.annotation.NamiClient;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.core.handle.Result;

import com.openlogic.auth.config.LoginConfig;
import com.openlogic.auth.domain.LoginBody;
import com.openlogic.auth.domain.RegisterBody;
import com.openlogic.common.core.constants.CacheConstant;
import com.openlogic.common.core.constants.UserConstant;
import com.openlogic.common.core.constants.basic.TenantConstants;
import com.openlogic.common.core.enums.DictEnum;
import com.openlogic.common.core.enums.LoginStatusType;
import com.openlogic.common.core.exception.ServiceException;
import com.openlogic.common.core.utils.ContextUtil;
import com.openlogic.common.core.utils.CryptoUtil;
import com.openlogic.common.core.web.domain.entity.SysUser;
import com.openlogic.common.core.web.domain.model.LoginUser;
import com.openlogic.common.redis.service.RedisService;
import com.openlogic.common.security.utils.SecurityUtil;
import com.openlogic.system.api.RemoteSysConfigService;
import com.openlogic.system.api.RemoteSysLogService;
import com.openlogic.system.api.RemoteSysUserService;
import com.openlogic.system.api.domain.SysLoginLog;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

/**
 * 认证处理
 * @author xm
 */
@Component
public class LoginService {

    @Inject
    LoginConfig loginConfig;

    @Inject
    CaptchaService captchaService;

    @Inject
    RedisService redisService;

    @NamiClient
    RemoteSysUserService remoteSysUserService;

    @NamiClient
    RemoteSysLogService remoteSysLogService;

    @NamiClient
    RemoteSysConfigService remoteSysConfigService;

    /**
     * 登录
     * @param loginBody
     * @param deviceType
     * @return
     */
    public Map<String, Object> login(LoginBody loginBody, String deviceType) {
        // 前置校验
        String password = checkLoginPre(loginBody, deviceType); // 用户密码
        String username = loginBody.getUsername(); // 用户名
        String enterpriseName = loginBody.getEnterpriseName(); // 租户名称

        // 查询用户
        Result<LoginUser> result = remoteSysUserService.getSysUserByUserName(enterpriseName, username);
        // 校验登录用户
        checkLoginUser(enterpriseName, username, password, result, deviceType);
        // 以上校验通过后，登录成功
        return loginSuccess(result.getData(), deviceType);
    }

    /**
     * 获取登录用户信息
     */
    public Map<String, Object> getUserInfo() {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        if(ObjectUtil.isNull(loginUser)) {
            throw new ServiceException("登录失效");
        }
        loginUser.getSysUser().setPassword(StrUtil.EMPTY);  // 清空密码
        Map<String, Object> map = new HashMap<>();
        map.put("user", loginUser.getSysUser());
        map.put("roles", loginUser.getRoles());
        map.put("permissions", loginUser.getPermissions());
        return map;
    }

    /**
     * 登出
     */
    public void logout() {
        String userName = SecurityUtil.getUserName();
        //TODO: 登出日志需要记录租户信息
        recordLoginLog(null,null,userName, LoginStatusType.LOGOUT.getCode(), "登出成功", SecurityUtil.getDeviceType());
        SecurityUtil.logout();
    }

    /**
     * 注册
     * @param registerBody
     * @param deviceType
     */
    public void register(RegisterBody registerBody, String deviceType) {
        // 是否开启注册用户
        Result<String> configResult = remoteSysConfigService.getSysConfigByKey("sys.account.registerUser");
        if(Result.SUCCEED_CODE != configResult.getCode() || !"true".equals(configResult.getData())) {
            throw new ServiceException("当前系统没有开启注册功能！");
        }

        // 前置校验
        String password = checkLoginPre(registerBody, deviceType);
        String username = registerBody.getUsername();

        SysUser sysUser = new SysUser();
        sysUser.setUserName(username);
        Result<Boolean> userResult = remoteSysUserService.checkUserNameUnique(sysUser);
        if(Result.SUCCEED_CODE != configResult.getCode()) {
            throw new ServiceException("注册失败！" + userResult.getDescription());
        } else if(!userResult.getData()) {
            throw new ServiceException("注册失败，账号已存在");
        }
        sysUser.setNickName(username);
        sysUser.setPassword(SecurityUtil.encryptPassword(password));
        sysUser.setStatus(DictEnum.COMMON_STATUS.OK.getValue());
        Result<Integer> integerResult = remoteSysUserService.registerUser(sysUser);
        if(Result.SUCCEED_CODE != integerResult.getCode()) {
            throw new ServiceException("注册失败！" + integerResult.getDescription());
        } else if(integerResult.getData() <= 0) {
            throw new ServiceException("注册失败，请联系系统管理人员");
        }
        //TODO: 注册功能需要添加租户
        recordLoginLog(null,null,username, LoginStatusType.REGISTER.getCode(), "注册成功", deviceType);
    }
    
//    private String checkLoginPre(LoginBody loginBody) {
//        // 1、校验验证码
//        captchaService.checkCapcha(loginBody.getCode(), loginBody.getUuid());
//
//        // 2、校验账号密码 企业账号||员工账号||密码为空 错误
//        String username = loginBody.getUsername();
//        String password = loginBody.getPassword();
//        
//        if(!StrUtil.isAllNotEmpty(username, password)) {
//            throw new ServiceException("账号/密码必须填写");
//        }
//        
//        // 解密密码
//        try {
//            password = CryptoUtil.rsaDecrypt(password);
//        } catch (Exception ignored) {
//        }
//        // 密码是否在指定长度范围内
//        if (password.length() < UserConstant.PASSWORD_MIN_LENGTH || password.length() > UserConstant.PASSWORD_MAX_LENGTH) {
//            throw new ServiceException("密码长度不在指定范围");
//        }
//        // 账号是否在指定长度范围内
//        if (username.length() < UserConstant.USERNAME_MIN_LENGTH || username.length() > UserConstant.USERNAME_MAX_LENGTH) {
//            throw new ServiceException("账号长度不在指定范围");
//        }
//
//        // 3、校验登录失败
//        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;
//        Object cacheTotal = redisService.getCacheObject(loginErrorKey);
//        if (ObjectUtil.isNotEmpty(cacheTotal)) {
//            // 剩余时间
//            long time = redisService.getCacheObjectExpire(loginErrorKey);
//            // 登录失败次数
//            long total = (Long) cacheTotal;
//            if(total >= loginConfig.getErrorCount()) {
//                long t = time / 1000 / 60 + 1L;
//                throw new ServiceException(String.format("账号已锁定，请 %s 分钟后再试", t));
//            }
//        }
//
//        return password;
//    }

    /**
     * 前置校验（验证码、账号密码、登录失败次数）
     * @param loginBody
     * @return 解密后的密码
     */
    @SuppressWarnings("all")
    private String checkLoginPre(LoginBody loginBody, String deviceType) {
        // 1、校验验证码
        captchaService.checkCapcha(loginBody.getCode(), loginBody.getUuid());

        // 2、校验账号密码 企业账号||员工账号||密码为空 错误
        String username = loginBody.getUsername();
        String password = loginBody.getPassword();
        String enterpriseName = loginBody.getEnterpriseName();
        
        if(!StrUtil.isAllNotEmpty(enterpriseName, username, password)) {
        	// j记录日志
            recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), "企业账号/员工账号/密码必须填写", deviceType);
            throw new ServiceException("企业账号/员工账号/密码必须填写");
        }
        
        // 解密密码
        try {
            password = CryptoUtil.rsaDecrypt(password);
        } catch (Exception ignored) {
        }
        // 密码是否在指定长度范围内
        if (password.length() < UserConstant.PASSWORD_MIN_LENGTH || password.length() > UserConstant.PASSWORD_MAX_LENGTH) {
            recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), "密码长度不在指定范围", deviceType);
            throw new ServiceException("密码长度不在指定范围");
        }
        // 账号是否在指定长度范围内
        if (username.length() < UserConstant.USERNAME_MIN_LENGTH || username.length() > UserConstant.USERNAME_MAX_LENGTH) {
        	recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), "账号长度不在指定范围", deviceType);
            throw new ServiceException("账号长度不在指定范围");
        }

        // 3、校验登录失败
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;
        Object cacheTotal = redisService.getCacheObject(loginErrorKey);
        if (ObjectUtil.isNotEmpty(cacheTotal)) {
            // 剩余时间
            long time = redisService.getCacheObjectExpire(loginErrorKey);
            // 登录失败次数
            long total = (Long) cacheTotal;
            if(total >= loginConfig.getErrorCount()) {
                long t = time / 1000 / 60 + 1L;
            	recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), String.format("账号已锁定，请 %s 分钟后再试", t), deviceType);
                throw new ServiceException(String.format("账号已锁定，请 %s 分钟后再试", t));
            }
        }

        return password;
    }

    /**
     * 校验登录用户
     * @param username
     * @param password
     * @param password2 
     * @param result
     * @param deviceType
     */
    @SuppressWarnings("all")
    private void checkLoginUser(String enterpriseName, String username, String password, Result<LoginUser> result, String deviceType) {
        if(Result.SUCCEED_CODE != result.getCode()) {
            loginError(username);
        }
        LoginUser loginUser = result.getData();
        SysUser sysUser = loginUser.getSysUser();
        if(DictEnum.DEL_FLAG.DELETED.getValue() == sysUser.getDelFlag()) {
            String msg = "对不起，您的账号已被删除";
            recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), msg, deviceType);
            throw new ServiceException(msg);
        }
        if(StrUtil.isBlank(sysUser.getStatus()) || DictEnum.COMMON_STATUS.DISABLE.getValue().equals(sysUser.getStatus())) {
            String msg = "账号已停用，请联系管理员";
            recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), msg, deviceType);
            throw new ServiceException(msg);
        }
        // 密码是否正确
        if(!SecurityUtil.matchesPassword(password, sysUser.getPassword())) {
            recordLoginLog(null, enterpriseName, username, LoginStatusType.LOGIN_FAIL.getCode(), "密码错误", deviceType);
            loginError(username);
        }
    }

    /**
     * 登录失败
     * @param username
     */
    private void loginError(String username) {
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + username;
        long total = 1L;
        Object cacheTotal = redisService.getCacheObject(loginErrorKey);
        if(ObjectUtil.isNotEmpty(cacheTotal)) {
            total = (Long) cacheTotal;
            total++;
        }
        redisService.setCacheObject(loginErrorKey, total, Duration.ofMinutes(loginConfig.getLockTime()));
        if(total >= loginConfig.getErrorCount()) {
            throw new ServiceException(String.format("账号已锁定，请 %s 分钟后再试", loginConfig.getLockTime()));
        } else {
            long n = loginConfig.getErrorCount() - total;
            throw new ServiceException(String.format("账号或密码错误，还剩 %s 次机会", n));
        }
    }

    /**
     * 登录成功
     * @param loginUser
     * @param deviceType 登录设备类型
     * @return
     */
    private Map<String, Object> loginSuccess(LoginUser loginUser, String deviceType) {

        NamiAttachment.put(TenantConstants.TENANT_ID, loginUser.getEnterpriseId());
        loginUser.setUserid(loginUser.getSysUser().getUserId());
        String username = loginUser.getSysUser().getUserName();
        loginUser.setUsername(username);
        loginUser.setDeviceType(deviceType);
        loginUser.setIpaddr(ContextUtil.getIpAddr());
        LoginUser login = SecurityUtil.login(loginUser, deviceType);
        // 删除登录失败
        String loginErrorKey = CacheConstant.LOGIN_ERROR_KEY + loginUser.getUsername();
        redisService.deleteCacheObject(loginErrorKey);
        recordLoginLog(loginUser.getEnterpriseId(), loginUser.getEnterpriseName(), username, LoginStatusType.LOGIN_SUCCESS.getCode(), "登录成功", deviceType);

        Map<String, Object> map = new HashMap<>();
        map.put("access_token", login.getToken());
        map.put("expires_in", login.getExpireTime());
        return map;
    }

    /**
     * 记录登录日志
     * @param username 账号
     * @param status 状态
     * @param message 消息内容
     * @param deviceType 登录设备类型
     * @return
     */
    public void recordLoginLog(String enterpriseId, String enterpriseName, String username, String status, String message, String deviceType) {
        SysLoginLog sysLoginLog = new SysLoginLog();
        sysLoginLog.setUserName(username);
        sysLoginLog.setIpaddr(ContextUtil.getIpAddr());
        sysLoginLog.setDeviceType(deviceType);
		sysLoginLog.setEnterpriseName(enterpriseName);
        sysLoginLog.setRemark(message);
        // 日志状态
        if (StrUtil.equalsAny(status, LoginStatusType.LOGIN_SUCCESS.getCode(), LoginStatusType.LOGOUT.getCode(), LoginStatusType.REGISTER.getCode())) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.SUCCESS.getValue());
        } else if (LoginStatusType.LOGIN_FAIL.getCode().equals(status)) {
            sysLoginLog.setStatus(DictEnum.COMMON_SUCCESS_FAIL.FAIL.getValue());
        }
        remoteSysLogService.saveLoginLog(sysLoginLog);
    }

}
